﻿Option Strict On

Imports System.Text.RegularExpressions
Imports System.Globalization
Imports System.Text

Namespace FTP
    ''' <summary>
    ''' Generic ftp file and directory listing parser that supports most Unix, Dos and Windows style FTP 
    ''' directory listings.  A custom parser can be created using the IFtpItemParser interface in the event
    ''' this parser does not suit the needs of a specific FTP server directory format listing.
    ''' </summary>
    Public Class FtpGenericParser
        Implements IFtpItemParser

        ' unix/microsoft regex expression
        Private _isUnix As Regex

        ' unix regex expressions
        Private _unixAttribs As Regex
        Private _unixMonth As Regex
        Private _unixDay As Regex
        Private _unixYear As Regex
        Private _unixTime As Regex
        Private _unixSize As Regex
        Private _unixName As Regex
        Private _unixSymbLink As Regex
        Private _unixType As Regex

        ' dos and other expressions
        Private _dosName As Regex
        Private _dosDate As Regex
        Private _dosTime As Regex
        Private _dosSize As Regex
        Private _dosDir As Regex


        ''' <summary>
        ''' Method to parse a line of file listing data from the FTP server.
        ''' </summary>
        ''' <param name="line">Line to parse.</param>
        ''' <return>Object representing data in parsed file listing line.</return>
        Public Function ParseLine(ByVal line As String) As FtpItem Implements IFtpItemParser.ParseLine
            If _isUnix Is Nothing Then _isUnix = New Regex("(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled)
            If _isUnix.IsMatch(line) Then
                Return ParseUnixFormat(line)
            Else
                Return ParseDosFormat(line)
            End If
        End Function

        Private Function ParseUnixFormat(ByVal line As String) As FtpItem
            'System.Diagnostics.Debug.Assert(! (line == "-r-xr-xr-x   1 root     root        21969 Apr  8  1986 rfc982.txt"));

            If _unixAttribs Is Nothing Then _unixAttribs = New Regex("(d|l|-|b|c|p|s)(r|w|x|-|t|s){9}", RegexOptions.Compiled)
            If _unixMonth Is Nothing Then _unixMonth = New Regex("(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixDay Is Nothing Then _unixDay = New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+)\d+", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixYear Is Nothing Then _unixYear = New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)(19|20)\d\d", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixTime Is Nothing Then _unixTime = New Regex("(?<=(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+)\d+:\d\d", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixSize Is Nothing Then _unixSize = New Regex("\d+(?=(\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)))", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixName Is Nothing Then _unixName = New Regex("((?<=((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+(19|20)\d\d\s+)|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|mrt|mei|okt)\s+\d+\s+\d+:\d\d\s+)).+)", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            If _unixSymbLink Is Nothing Then _unixSymbLink = New Regex("(?<=\s+->\s+).+", RegexOptions.Compiled)
            If _unixType Is Nothing Then _unixType = New Regex("(d|l|-|b|c|p|s)(?=(r|w|x|-|t|s){9})", RegexOptions.Compiled)


            Dim attribs As String = _unixAttribs.Match(line).ToString()
            Dim month As String = _unixMonth.Match(line).ToString()
            Dim day As String = _unixDay.Match(line).ToString()
            Dim year As String = _unixYear.Match(line).ToString()
            Dim time As String = _unixTime.Match(line).ToString()
            Dim size As String = _unixSize.Match(line).ToString()
            Dim name As String = _unixName.Match(line).ToString().Trim()
            Dim symbLink As String = ""

            ' ignore the microsoft 'etc' file that IIS uses for WWW users
            If name = "~ftpsvc~.ckm" Then
                Return Nothing
            End If

            '  if we find a symbolic link then extract the symbolic link first and then
            '  extract the file name portion
            If _unixSymbLink.IsMatch(name) Then
                symbLink = _unixSymbLink.Match(name).ToString()
                name = name.Substring(0, name.IndexOf("->")).Trim()
            End If

            Dim itemType As String = _unixType.Match(line).ToString()


            '  if the current year is not given in unix then we need to figure it out.
            '  basically, if a date is within the past 6 months unix will show the 
            '  time instead of the year
            If year.Length = 0 Then
                Dim curMonth As Integer = DateTime.Today.Month
                Dim curYear As Integer = DateTime.Today.Year

                Dim result As DateTime
                If DateTime.TryParse(String.Format(CultureInfo.InvariantCulture, "1-{0}-2007", month), result) Then
                    If (curMonth - result.Month) < 0 Then
                        year = Convert.ToString(curYear - 1, CultureInfo.InvariantCulture)
                    Else
                        year = curYear.ToString(CultureInfo.InvariantCulture)
                    End If
                End If
            End If

            Dim dateObj As DateTime
            DateTime.TryParse(String.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2} {3}", day, month, year, time), dateObj)

            Dim sizeLng As Long = 0
            Int64.TryParse(size, sizeLng)

            Dim itemTypeObj As FtpItemType = FtpItemType.Unknown
            Select Case itemType.ToLower(CultureInfo.InvariantCulture)
                Case "l"
                    itemTypeObj = FtpItemType.SymbolicLink
                    Exit Select
                Case "d"
                    itemTypeObj = FtpItemType.Directory
                    Exit Select
                Case "-"
                    itemTypeObj = FtpItemType.File
                    Exit Select
                Case "b"
                    itemTypeObj = FtpItemType.BlockSpecialFile
                    Exit Select
                Case "c"
                    itemTypeObj = FtpItemType.CharacterSpecialFile
                    Exit Select
                Case "p"
                    itemTypeObj = FtpItemType.NamedSocket
                    Exit Select
                Case "s"
                    itemTypeObj = FtpItemType.DomainSocket
                    Exit Select
            End Select

            If itemTypeObj = FtpItemType.Unknown OrElse name.Trim().Length = 0 Then
                Return Nothing
            Else
                Return New FtpItem(name, dateObj, sizeLng, symbLink, attribs, itemTypeObj, _
                 line)
            End If
        End Function

        Private Function ParseDosFormat(ByVal line As String) As FtpItem


            '  Dim name As String = _dosName.Match(line).ToString().Trim()

            If _dosName Is Nothing Then _dosName = New Regex("((?<=<DIR>\s+).+)|((?<=\d\d:\d\d\s+).+)|((?<=(\d\d:\d\d(AM|PM|am|pm)\s+\d+\s+)).+)", RegexOptions.Compiled)
            If _dosDate Is Nothing Then _dosDate = New Regex("(\d\d-\d\d-\d\d)", RegexOptions.Compiled)
            If _dosTime Is Nothing Then _dosTime = New Regex("(\d\d:\d\d\s*(AM|PM|am|pm))|(\d\d:\d\d)", RegexOptions.Compiled)
            If _dosSize Is Nothing Then _dosSize = New Regex("((?<=(\d\d:\d\d\s*(AM|PM|am|pm)\s*))\d+)|(\d+(?=\s+\d\d-\d\d-\d\d\s+))", RegexOptions.Compiled)
            If _dosDir Is Nothing Then _dosDir = New Regex("<DIR>|\sDIR\s", RegexOptions.Compiled)

            Dim name As String = _dosName.Match(line).ToString().Trim()

            ' if the name has no length the simply stop processing and return null.
            If name.Trim().Length = 0 Then
                Return Nothing
            End If

            Dim [date] As String = _dosDate.Match(line).ToString()
            Dim time As String = _dosTime.Match(line).ToString()
            Dim size As String = _dosSize.Match(line).ToString()
            Dim dir As String = _dosDir.Match(line).ToString().Trim()

            ' put togther the date/time
            Dim dateTime__1 As DateTime = DateTime.MinValue
            DateTime.TryParse(String.Format(CultureInfo.InvariantCulture, "{0} {1}", [date], time), dateTime__1)

            ' parse the file size
            Dim sizeLng As Long = 0
            Int64.TryParse(size, sizeLng)

            ' determine the file item itemType
            Dim itemTypeObj As FtpItemType
            If dir.Length > 0 Then
                itemTypeObj = FtpItemType.Directory
            Else
                itemTypeObj = FtpItemType.File
            End If

            Return New FtpItem(name, dateTime__1, sizeLng, String.Empty, String.Empty, itemTypeObj, _
             line)
        End Function

        Private Function GetLocalMonthAbrevList(ByVal culture As String) As String
            Dim sb As New StringBuilder()
            Dim engMonths As String() = {"jan", "feb", "mar", "apr", "may", "jun", _
             "jul", "aug", "sep", "oct", "nov", "dec"}

            'CultureInfo current = CultureInfo.CurrentCulture;
            Dim current As CultureInfo = CultureInfo.GetCultureInfo(culture)

            Dim months As String() = current.DateTimeFormat.AbbreviatedMonthNames

            For i As Integer = 0 To 11
                If String.Compare(months(i), engMonths(i), StringComparison.InvariantCultureIgnoreCase) <> 0 Then
                    sb.Append(months(i))
                    sb.Append("|")
                End If
            Next

            Dim list As String = sb.ToString()
            If list.Length < 0 Then
                list = list.Remove(list.Length - 1, 1)
            End If

            Return list
        End Function
    End Class
End Namespace